/*!
    \copyright  Copyright (c) 2019 - 2025 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file       
    \ingroup    pairing
    \brief      Pairing plugin interface
*/
#include <list.h> // This include needs to be first because it includes stdbool.h and else it creates a lot of issues with the bool type
#include "pairing.h"
#include "pairing_plugin.h"
#include "pairing_plugin_default.h"
#include "pairing_plugin_sm.h"
#include "pairing_private.h"
#include <panic.h>
#include <stdlib.h>
#include <logging.h>
#include <bdaddr.h>

#define PAIRING_PLUGINS_MAX 3

typedef enum
{
    pairing_plugin_state_idle,
    pairing_plugin_state_remote_io_capability,
    pairing_plugin_state_io_capability_rsp,
    pairing_plugin_state_user_confirmation,
    pairing_plugin_state_ctkd,
    pairing_plugin_state_ssp_complete,
} pairing_plugin_state_t;

typedef union
{
    CsrBtCmSmIoCapabilityResponseInd    remote_io_capability_ind;
    CsrBtCmSmIoCapabilityRequestInd     io_capability_ind;
    CsrBtCmSmUserConfirmationRequestInd user_confirmation_ind;
    CmSmGenerateCrossTransKeyRequestInd ctkd_ind;
    CsrBtCmSmSimplePairingCompleteInd   ssp_complete_ind;
} pairing_indication_t;

typedef union
{
    pairing_io_capability_rsp_t         io_capability_rsp;
    pairing_user_confirmation_rsp_t     user_confirmation_rsp;
    pairing_ctkd_rsp_t                  ctkd_rsp;
    pairing_ssp_complete_rsp_t          ssp_complete_rsp;
} pairing_response_t;

typedef struct __pairing_plugin_context_t
{
    pairing_plugin_state_t                  state;
    pairing_indication_t                    ind;
    pairing_response_t                      rsp;
    unsigned                                index;
    pairing_plugin_t*                       plugins[PAIRING_PLUGINS_MAX];
} __pairing_plugin_context_t;

extern pairing_plugin_t pairing_plugin;

static const list_config_t pairing_plugin_context_list_cfg = {
    .type = list_type_linked_single,
    .element_store.size = sizeof(__pairing_plugin_context_t),
    .element_type = ADK_LIST_ELEMENT_TYPEOF(__pairing_plugin_context_t)
};

list_t pairing_plugin_context_list;

static void pairing_PluginSmContextReset(pairing_plugin_context_t context)
{
    pairingTaskData *thePairing = PairingGetTaskData();
    
    memset(context, 0, sizeof(struct __pairing_plugin_context_t));
    context->plugins[0] = &pairing_plugin_default;
    context->plugins[1] = &thePairing->pairing_clients[0].params.client_plugin;
    context->plugins[2] = &pairing_plugin;
    context->index = 0;
}

static bool pairing_PluginGetTpaddrFromContext(pairing_plugin_context_t context, tp_bdaddr* tp_addr)
{
    bool addr_available = FALSE;
    PanicNull(tp_addr);
    
    switch(context->state)
    {
        case pairing_plugin_state_io_capability_rsp:
            BdaddrConvertTpBluestackToVm(tp_addr, &context->ind.io_capability_ind.tp_addrt);
            addr_available = TRUE;
        break;
        
        case pairing_plugin_state_user_confirmation:
            BdaddrConvertTpBluestackToVm(tp_addr, &context->ind.user_confirmation_ind.tp_addrt);
            addr_available = TRUE;
        break;
        
        case pairing_plugin_state_ctkd:
            BdaddrConvertTpBluestackToVm(tp_addr, &context->ind.ctkd_ind.tp_addrt);
            addr_available = TRUE;
        break;
        
        case pairing_plugin_state_ssp_complete:
            BdaddrConvertTpBluestackToVm(tp_addr, &context->ind.ssp_complete_ind.tp_addrt);
            addr_available = TRUE;
        break;
        
        default:
        break;
    }
    return addr_available;
}

static pairing_plugin_context_t pairing_PluginNewContext(void)
{
    __pairing_plugin_context_t new_context;
    pairing_PluginSmContextReset(&new_context);

    ListAppend(&pairing_plugin_context_list, &new_context);
    ListGotoTail(&pairing_plugin_context_list);
    return ListGetCurrentElementAddress(&pairing_plugin_context_list);
}

static void pairing_PluginFreeContext(pairing_plugin_context_t context)
{
    LIST_FOREACH(&pairing_plugin_context_list)
    {
        if(ListGetCurrentElementAddress(&pairing_plugin_context_list) == context)
        {
            ListRemoveCurrentElement(&pairing_plugin_context_list);
            break;
        }
    }
}

void Pairing_PluginSmContextInit(void)
{
    pairing_plugin_context_list = ListCreate(&pairing_plugin_context_list_cfg);
}

pairing_plugin_context_t Pairing_PluginSmSetRemoteIoCapability(const CsrBtCmSmIoCapabilityResponseInd *ind)
{
    pairing_plugin_context_t context = pairing_PluginNewContext();
    context->state = pairing_plugin_state_remote_io_capability;
    context->ind.remote_io_capability_ind = *ind;
    return context;
}

void Pairing_PluginSmRemoteIoCapabilityCallbacks(pairing_plugin_context_t context)
{
    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmRemoteIoCapabilityCallbacks (%p)", context);
    
    if(context && context->state == pairing_plugin_state_remote_io_capability)
    {
        const CsrBtCmSmIoCapabilityResponseInd* ind = &context->ind.remote_io_capability_ind;
        
        for(context->index = 0; context->index < PAIRING_PLUGINS_MAX; context->index++)
        {
            if(Pairing_PluginHandleRemoteIoCapability(context->plugins[context->index], ind))
            {
                DEBUG_LOG_VERBOSE("pairing_ProcessIoCapabilityRequestInd handled by plugins[%d]", context->index);
            }
        }
        pairing_PluginFreeContext(context);
    }
}

pairing_plugin_context_t Pairing_PluginSmSetIoCapabilityRequestInd(const CsrBtCmSmIoCapabilityRequestInd* ind)
{
    pairing_plugin_context_t context = pairing_PluginNewContext();
    context->state = pairing_plugin_state_io_capability_rsp;
    context->ind.io_capability_ind = *ind;
    return context;
}

bool Pairing_PluginSmGetIoCapabilityRequestInd(pairing_plugin_context_t context, CsrBtCmSmIoCapabilityRequestInd* ind)
{
    if(context && context->state == pairing_plugin_state_io_capability_rsp)
    {
        *ind = context->ind.io_capability_ind;
        return TRUE;
    }
    DEBUG_LOG_ERROR("Pairing_PluginSmGetIoCapabilityRequestInd invalid context (%p)", context);
    return FALSE;
}

pairing_plugin_rsp_t Pairing_PluginSmIoCapabilityRequestCallbacks(pairing_plugin_context_t context, pairing_io_capability_rsp_t* io_capability_rsp)
{
    CsrBtCmSmIoCapabilityRequestInd ind;
    pairing_plugin_rsp_t response = pairing_plugin_response_abstain;
    
    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmIoCapabilityRequestCallbacks (%p)", context);
    
    if(context && Pairing_PluginSmGetIoCapabilityRequestInd(context, &ind))
    {
        pairing_io_capability_rsp_t rsp = context->rsp.io_capability_rsp;
        
        for(context->index = 0; context->index < PAIRING_PLUGINS_MAX; context->index++)
        {
            if(Pairing_PluginHandleIoCapabilityRequest(context->plugins[context->index], &ind, &response, &rsp))
            {
                DEBUG_LOG_VERBOSE("pairing_ProcessIoCapabilityRequestInd handled by plugins[%d]", context->index);
            }
            
            if(response == pairing_plugin_response_update)
            {
                DEBUG_LOG_VERBOSE("pairing_ProcessIoCapabilityRequestInd enum:cl_sm_io_capability:%d, enum:mitm_setting:%d, bonding %d, key_distribution %d, oob_data %d", 
                                                                         rsp.io_capability,           rsp.mitm,             (int)rsp.bonding,rsp.key_distribution,rsp.oob_data);
                context->rsp.io_capability_rsp = rsp;
            }
            else if(response == pairing_plugin_response_wait)
            {
                DEBUG_LOG_VERBOSE("pairing_ProcessIoCapabilityRequestInd wait");
                break;
            }
        }
        
        *io_capability_rsp = context->rsp.io_capability_rsp;
        
        if(context->index == PAIRING_PLUGINS_MAX)
        {
            pairing_PluginFreeContext(context);
        }
        
    }
    return response;
}

pairing_plugin_context_t Pairing_PluginSmSetUserConfirmationInd(const CsrBtCmSmUserConfirmationRequestInd* ind)
{
    pairing_plugin_context_t context = pairing_PluginNewContext();
    context->state = pairing_plugin_state_user_confirmation;
    context->ind.user_confirmation_ind = *ind;
    return context;
}

bool Pairing_PluginSmGetUserConfirmationInd(pairing_plugin_context_t context, CsrBtCmSmUserConfirmationRequestInd* ind)
{
    if(context && context->state == pairing_plugin_state_user_confirmation)
    {
        *ind = context->ind.user_confirmation_ind;
        return TRUE;
    }
    DEBUG_LOG_ERROR("Pairing_PluginSmGetUserConfirmationInd invalid context (%p)", context);
    return FALSE;
}

pairing_plugin_rsp_t Pairing_PluginSmUserConfirmationRequestCallbacks(pairing_plugin_context_t context, pairing_user_confirmation_rsp_t* user_confirmation_rsp)
{
    CsrBtCmSmUserConfirmationRequestInd ind;
    pairing_plugin_rsp_t response = pairing_plugin_response_abstain;
    
    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmUserConfirmationRequestCallbacks (%p)", context);
    
    if(context && Pairing_PluginSmGetUserConfirmationInd(context, &ind))
    {
        pairing_user_confirmation_rsp_t rsp = {0};
        
        for(context->index = 0; context->index < PAIRING_PLUGINS_MAX; context->index++)
        {
            if(Pairing_PluginHandleUserConfirmationRequest(context->plugins[context->index], &ind, &response, &rsp))
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmUserConfirmationRequestCallbacks handled by plugins[%d]", context->index);
            }
            
            if(response == pairing_plugin_response_update)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmUserConfirmationRequestCallbacks accept %d", (int)rsp.accept);
                context->rsp.user_confirmation_rsp = rsp;
            }
            else if(response == pairing_plugin_response_wait)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmUserConfirmationRequestCallbacks wait");
                break;
            }
        }
        
        *user_confirmation_rsp = context->rsp.user_confirmation_rsp;
        
        if(context->index == PAIRING_PLUGINS_MAX)
        {
            pairing_PluginFreeContext(context);
        }
    }
    return response;
}

pairing_plugin_context_t Pairing_PluginSmSetCtkdInd(const CmSmGenerateCrossTransKeyRequestInd* ind)
{
    pairing_plugin_context_t context = pairing_PluginNewContext();
    context->state = pairing_plugin_state_ctkd;
    context->ind.ctkd_ind = *ind;
    return context;
}

bool Pairing_PluginSmGetCtkdInd(pairing_plugin_context_t context, CmSmGenerateCrossTransKeyRequestInd* ind)
{
    if(context && context->state == pairing_plugin_state_ctkd)
    {
        *ind = context->ind.ctkd_ind;
        return TRUE;
    }
    DEBUG_LOG_ERROR("Pairing_PluginSmGetCtkdInd invalid context (%p)", context);
    return FALSE;
}

pairing_plugin_rsp_t Pairing_PluginSmCtkdCallbacks(pairing_plugin_context_t context, pairing_ctkd_rsp_t* ctkd_rsp)
{
    CmSmGenerateCrossTransKeyRequestInd ind;
    pairing_plugin_rsp_t response = pairing_plugin_response_abstain;
    
    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmCtkdCallbacks (%p)", context);
    
    if(context && Pairing_PluginSmGetCtkdInd(context, &ind))
    {
        pairing_ctkd_rsp_t rsp = context->rsp.ctkd_rsp;
        
        for(context->index = 0; context->index < PAIRING_PLUGINS_MAX; context->index++)
        {
            if(Pairing_PluginHandleCrossTransKeyInd(context->plugins[context->index], &ind, &response, &rsp))
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmCtkdCallbacks handled by plugins[%d]", context->index);
            }
            
            if(response == pairing_plugin_response_update)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmCtkdCallbacks allow_ctkd %d", (int)rsp.allow_ctkd);
                context->rsp.ctkd_rsp = rsp;
            }
            else if(response == pairing_plugin_response_wait)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmCtkdCallbacks wait");
                break;
            }
        }
        
        *ctkd_rsp = context->rsp.ctkd_rsp;
        
        if(context->index == PAIRING_PLUGINS_MAX)
        {
            pairing_PluginFreeContext(context);
        }
    }
    return response;
}

pairing_plugin_context_t Pairing_PluginSmSetSspCompleteInd(const CsrBtCmSmSimplePairingCompleteInd* ind)
{
    pairing_plugin_context_t context = pairing_PluginNewContext();
    context->state = pairing_plugin_state_ssp_complete;
    context->ind.ssp_complete_ind = *ind;
    return context;
}

bool Pairing_PluginSmGetSspCompleteInd(pairing_plugin_context_t context, CsrBtCmSmSimplePairingCompleteInd* ind)
{
    if(context && context->state == pairing_plugin_state_ssp_complete)
    {
        *ind = context->ind.ssp_complete_ind;
        return TRUE;
    }
    DEBUG_LOG_ERROR("Pairing_PluginSmGetSspCompleteInd invalid context (%p)", context);
    return FALSE;
}

pairing_plugin_rsp_t Pairing_PluginSmSspCompleteIndicationCallbacks(pairing_plugin_context_t context, pairing_ssp_complete_rsp_t* ssp_complete_rsp)
{
    CsrBtCmSmSimplePairingCompleteInd ind;
    pairing_plugin_rsp_t response = pairing_plugin_response_abstain;
    
    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmSspCompleteIndicationCallbacks (%p)", context);
    
    if(context && Pairing_PluginSmGetSspCompleteInd(context, &ind))
    {
        pairing_ssp_complete_rsp_t rsp = context->rsp.ssp_complete_rsp;
        
        for(context->index = 0; context->index < PAIRING_PLUGINS_MAX; context->index++)
        {
            if(Pairing_PluginHandleSppCompleteIndication(context->plugins[context->index], &ind, &response, &rsp))
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmSspCompleteIndicationCallbacks handled by plugins[%d]", context->index);
            }
            
            if(response == pairing_plugin_response_update)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmSspCompleteIndicationCallbacks enum:pairingBlePermission:%d", rsp.ble_permission);
                context->rsp.ssp_complete_rsp = rsp;
            }
            else if(response == pairing_plugin_response_wait)
            {
                DEBUG_LOG_VERBOSE("Pairing_PluginSmSspCompleteIndicationCallbacks wait");
                break;
            }
        }
        
        *ssp_complete_rsp = context->rsp.ssp_complete_rsp;
        
        if(context->index == PAIRING_PLUGINS_MAX)
        {
            pairing_PluginFreeContext(context);
        }
    }
    return response;
}

void Pairing_PluginSmPairingComplete(const tp_bdaddr* tp_addr)
{
    /* Notify all plugins pairing has completed */
    __pairing_plugin_context_t tmp_context;
    pairing_PluginSmContextReset(&tmp_context);

    DEBUG_LOG_FN_ENTRY("Pairing_PluginSmPairingComplete");
    
    for(tmp_context.index = 0; tmp_context.index < PAIRING_PLUGINS_MAX; tmp_context.index++)
    {
        Pairing_PluginPairingComplete(tmp_context.plugins[tmp_context.index]);
        DEBUG_LOG_VERBOSE("Pairing_PluginSmPairingComplete completed plugins[%d]", tmp_context.index);
    }

    /* Mark any context waiting on this transport/address as expired because pairing must have timed out */
    ListGotoHead(&pairing_plugin_context_list);
    while(!ListIsAtEndOfList(&pairing_plugin_context_list))
    {
        tp_bdaddr context_addr;
        pairing_plugin_context_t context = ListGetCurrentElementAddress(&pairing_plugin_context_list);
        if(pairing_PluginGetTpaddrFromContext(context, &context_addr) && BdaddrTpIsSame(&context_addr, tp_addr))
        {
            ListRemoveCurrentElement(&pairing_plugin_context_list);
        }
        else
        {
            ListGotoNext(&pairing_plugin_context_list);
        }
    }
}

static bool pairing_PluginContextInList(pairing_plugin_context_t context)
{
    bool in_list = FALSE;
    LIST_FOREACH(&pairing_plugin_context_list)
    {
        pairing_plugin_context_t context_in_list = ListGetCurrentElementAddress(&pairing_plugin_context_list);
        if(context_in_list == context)
        {
            in_list = TRUE;
            break;
        }
    }
    DEBUG_LOG("pairing_PluginContextInList, context=0x%p, in_list=%d", context, (int)in_list);
    return in_list;
}

bool Pairing_PluginRetry(void)
{
    bool retried = FALSE;
    DEBUG_LOG_FN_ENTRY("Pairing_PluginRetry");

    list_config_t iter_list_cfg = {
        .type = list_type_linked_single,
        .element_store.size = sizeof(void*),
    };
    list_t iter_list = ListCreate(&iter_list_cfg);
    LIST_FOREACH(&pairing_plugin_context_list)
    {
        pairing_plugin_context_t context = ListGetCurrentElementAddress(&pairing_plugin_context_list);
        ListAppend(&iter_list, &context);
    }

    LIST_FOREACH(&iter_list)
    {
        pairing_plugin_context_t context; 
        ListGetCurrentElement(&iter_list, &context);

        if (!pairing_PluginContextInList(context))
        {
            continue;
        }
        

        DEBUG_LOG_INFO("Pairing_PluginRetry (%p) enum:pairing_plugin_state_t:%d", context, context->state);
        
        switch(context->state)
        {
            case pairing_plugin_state_io_capability_rsp:
                pairing_ProcessIoCapabilityRequestInd(context);
                retried = TRUE;
            break;
            
            case pairing_plugin_state_user_confirmation:
                pairing_ProcessUserConfirmationInd(context);
                retried = TRUE;
            break;
            
            case pairing_plugin_state_ctkd:
                pairing_ProcessCrossTransKeyInd(context);
                retried = TRUE;
            break;
            
            case pairing_plugin_state_ssp_complete:
                pairing_ProcessSspCompleteInd(context);
                retried = TRUE;
            break;
            
            default:
            break;
        }
    }
    ListDestroy(&iter_list);

   return retried;
}
